﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Zhy.ModbusLib.Base;

namespace Zhy.ModbusLib
{
    /// <summary>
    /// Modbus协议通讯抽象类
    /// </summary>
    public abstract class ModbusMaster
    {
        #region 字段
        internal List<AsyncModel> asyncModels = new List<AsyncModel>();
        #endregion

        /// <summary>
        /// 连接
        /// </summary>
        public virtual void Connect() { }
        /// <summary>
        /// 关闭连接
        /// </summary>
        public virtual void Disconnect() { }

        #region 读取数据
        /// <summary>
        /// 读取 4个存储区域
        //  从站地址、功能码、起始地址、数量
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始位置</param>
        /// <param name="count">读取个数</param>
        /// <returns></returns>
        public virtual byte[] Read(byte slave, Functions func, ushort start, ushort count)
        {
            return null;
        }

        /// <summary>
        /// 读取 4个存储区域
        /// 从站地址、功能码、起始地址、数量
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始位置</param>
        /// <param name="count">读取个数</param>
        /// <param name="handler">请求ID</param>
        /// <param name="callback">委托用于处理返回的数据</param>
        public virtual void ReadAsync(
            byte slave, Functions func, ushort start, ushort count,
            int handler, Action<byte[], int, Exception> callback)
        {
        }

        // 00001
        // 10001
        // 30001
        // 40001
        /// <summary>
        /// 这个方法用于读取一个数据
        /// 通过字符串解析地址，读取数据
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="variable">功能码</param>
        /// <param name="isBaseOne">false:起始地址从0开始 true：起始地址从1开始</param>
        /// <returns></returns>
        public byte[] Read(byte slave, string variable, bool isBaseOne = false)
        {
            // 获取功能码和起始位置
            (Functions, int) result = this.GetAddress(variable, true, isBaseOne);
            // 读取一个地址的报文
            return this.Read(slave, result.Item1, (ushort)result.Item2, 1);
        }
        #endregion

        #region 写入数据
        /// <summary>
        /// Modbus RTU 写入操作
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">字节数</param>
        /// <param name="datas">写入数据</param>
        public virtual void Write(byte slave, Functions func, ushort start, ushort count, byte[] datas) { }

        /// <summary>
        /// Modbus RTU 写入操作
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">字节数</param>
        /// <param name="datas">写入数据</param>
        /// <param name="callback">委托用于处理返回的数据</param>
        public virtual void WriteAsync(
           byte slave, Functions func, ushort start, ushort count, byte[] datas,
           int handler, Action<byte[], int, Exception> callback)
        { }

        /// <summary>
        /// 这个方法用于写入一个数据
        /// 通过字符串解析地址，写入数据
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="variable">功能码</param>
        /// <param name="count">写入个数</param>
        /// <param name="datas">写入数据</param>
        /// <param name="isBaseOne">false:起始地址从0开始 true：起始地址从1开始</param>
        public void Write(byte slave, string variable, ushort count, byte[] datas, bool isBaseOne = false)
        {
            // 获取功能码和起始位置
            (Functions, int) result = this.GetAddress(variable, false, isBaseOne);
            // 写入报文数据
            this.Write(slave, result.Item1, (ushort)result.Item2, count, datas);
        }
        #endregion

        #region Modbus - 读取的发送报文拼接
        /// <summary>
        /// Modbus - 读取的发送报文拼接
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="funcCode">功能码</param>
        /// <param name="start">开始地址</param>
        /// <param name="count">获取个数</param>
        /// <returns></returns>
        public byte[] GetReadBytes(byte slave, byte funcCode, ushort start, ushort count)
        {
            return new byte[] {
               slave, // 从站地址
               funcCode, // 功能码
               (byte)(start / 256), // 起始地址-高位
               (byte)(start % 256), // 起始地址-地位
               (byte)(count / 256), // 读取个数 - 高位
               (byte)(count % 256) // 读取个数 - 低位
            };
        }
        #endregion

        #region Modbus - 写入的发送报文拼接
        /// <summary>
        /// Modbus - 写入的发送报文拼接
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="funcCode">功能码</param>
        /// <param name="start">开始地址</param>
        /// <param name="count">写入数量</param>
        /// <param name="datas">写入报文</param>
        /// <returns></returns>
        public byte[] GetWriteBytes(byte slave, byte funcCode, ushort start, int count, byte[] datas)
        {
            // 线圈/寄存器    多写功能码进行处理
            List<byte> bytes = new List<byte>()
            {
               slave, // 从站地址
                funcCode, // 功能码
                (byte)(start / 256), // 写入地址高位
                (byte)(start % 256), // 写入地址低位
                /// 寄存器数量   datas.lenght/2
                /// 线圈数量
                (byte)(count/256), // 写入数量高位 0-65280
                (byte)(count%256), // 写入数量低位 0-255
                (byte)datas.Length, // 字节数
            };
            bytes.AddRange(datas); // 添加要写入的数据

            return bytes.ToArray(); // 返回字节数组
        }
        #endregion

        #region 发送和接收报文数据方法
        /// <summary>
        /// 发送和接收报文数据方法
        /// </summary>
        /// <param name="bytes">请求报文</param>
        /// <param name="len">正常返回报文字节数</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected virtual byte[] SendAndReceive(byte[] bytes, int len) { return null; }
        #endregion


        /// <summary>
        /// 读取时将字节数组转成10进制
        /// 根据所提供的字节数组进行数据转换，通过泛型进行数据类型的指定
        /// </summary>
        /// <typeparam name="T">指定类型</typeparam>
        /// <param name="bytes">需要转换的字节数组</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static T[] GetDatas<T>(byte[] bytes, EndianType endianType = EndianType.ABCD)
        {
            if (typeof(T) == typeof(bool)) // 转换线圈读取数据
            {
                List<T> values = new List<T>();
                for (int i = 0; i < bytes.Length; i++) // 外边循环的是字节，一个字节有8位
                {
                    // 0000 0000
                    // 0000 0100    &
                    for (int j = 0; j < 8; j++) // 循环的是一个字节的8位
                    {
                        // bytes[i] & 1 >0 是true
                        // (1 << j) 每次1向左移动的位数
                        dynamic dd = (bytes[i] & (1 << j)) > 0;
                        values.Add(dd); // 添加数据 - 可以自动转成bool类型
                    }
                }
                return values.ToArray();
            }
            else // 转换寄存器读取数据
            {
                List<T> values = new List<T>();
                // 2字节   ushort  short
                // 4bytes  float  int  

                int len = Marshal.SizeOf(typeof(T)); // 获取需要转换类型的大小
                if (bytes.Length % len > 0) // 需要类型字节可以整除才能转换
                    throw new Exception("需要转换的字节数量无效");
                // 获取转类中所有的公开的静态类方法
                Type tbc = typeof(BitConverter);
                MethodInfo[] mis = tbc.GetMethods(
                     BindingFlags.Public | BindingFlags.Static);
                // 获取与指定类型相同的并且有两个参数的方法
                MethodInfo method = mis.FirstOrDefault(
                    mi => mi.ReturnType == typeof(T) 
                    && mi.GetParameters().Count() == 2);

                if (method == null) // 判断方法是否存在
                    throw new Exception("数据转换出错！未找到匹配的数据转换方法");

                for (int i = 0; i < bytes.Length; i += len) // 循环截取字节
                {
                    List<byte> dataTemp = bytes.ToList().GetRange(i, len);  // 截取字节
                                                                            // 调整字节序
                    byte[] sb = SwitchEndianType(dataTemp, endianType);
                    // short/ushort
                    //ushort us = BitConverter.ToUInt16(dataTemp.ToArray(), 0);
                    //float f = BitConverter.ToSingle(dataTemp.ToArray(), 0);
                    // 通过对应的BitConverter方法转换Byte[]数组到数据
                    // BitConverter  小端
                    if (BitConverter.IsLittleEndian) // 判断如果是小端进行翻转
                        sb = sb.Reverse().ToArray();

                    // 将数据按照指定类型转换后添加
                    values.Add((T)method.Invoke(tbc, new object[] { sb, 0 }));
                }

                return values.ToArray(); // 返回转换后的数据
            }
        }

        /// <summary>
        /// 写入时将10进制转成字节数组
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="datas"></param>
        /// <param name="endianType"></param>
        /// <returns></returns>
        public static byte[] GetBytes<T>(T[] datas, EndianType endianType = EndianType.ABCD)
        {
            List<byte> result = new List<byte>(); // 字节数组

            // 5个状态
            // 0000 0000
            // 10个状态
            // 0000 0000     0000 0000
            // 0000 0001
            //         1
            if (typeof(T) == typeof(bool))  // 转换线圈写入数据
            {
                byte start = 0x00; // 初始化一个字节的线圈
                for (int i = 0; i < datas.Length; i++) // 循环线圈数据
                {
                    var item = datas[i]; // 写入线圈项
                    byte bit = (byte)(bool.Parse(item.ToString()) ? 1 : 0); // 计算线圈项值1:true 0:false
                    bit = (byte)(bit << (i % 8)); // 移动线圈位

                    start |= bit; // 线圈项值赋值到字节中

                    if ((i % 8) == 7)
                    {
                        result.Add(start); // 添加一个字节线圈值
                        start = 0x00; // 初始化一个字节的线圈
                    }
                }
                // 判断
                if (datas.Length % 8 > 0) // 添加不足一个字节的线圈值 
                    result.Add(start);
            }
            else // 转换寄存器写入数据
            {
                foreach (dynamic item in datas) // 循环要写入的寄存器数据
                {
                    byte[] db = BitConverter.GetBytes(item); // 将数据项转换成字节数组

                    if (BitConverter.IsLittleEndian) // 判断如果是小端进行翻转
                        db = db.Reverse().ToArray();

                    // ABCD 调整字节数组顺序
                    db = SwitchEndianType(db.ToList(), endianType); 

                    result.AddRange(db); // 添加字节数组
                }
            }

            return result.ToArray(); // 返回要发送的字节数组数据
        }

        /// <summary>
        /// 调整字节数组顺序
        /// </summary>
        /// <param name="datas"></param>
        /// <param name="endianType"></param>
        /// <returns></returns>
        private static byte[] SwitchEndianType(List<byte> datas, EndianType endianType)
        {
            switch (endianType)
            {
                case EndianType.ABCD: // 大端排序
                case EndianType.ABCDEFGH: // 大端排序
                    return datas.ToArray();
                case EndianType.CDAB: // 小端排序
                    return new byte[] { datas[2], datas[3], datas[0], datas[1] };
                case EndianType.BADC:
                    return new byte[] { datas[1], datas[0], datas[3], datas[2] };

                case EndianType.GHEFCDAB:
                    return new byte[] { datas[6], datas[7], datas[4], datas[5] ,
                        datas[2], datas[3], datas[0], datas[1] };
                case EndianType.BADCFEHG:
                    return new byte[] { datas[1], datas[0], datas[3], datas[2] ,
                        datas[5], datas[4], datas[7], datas[6] };
                case EndianType.DCBA: 
                case EndianType.HGFEDCBA:
                    datas.Reverse();
                    return datas.ToArray();
            }
            return datas.ToArray();
        }

        /// <summary>
        /// 分解Web功能码 - 获取功能码
        /// </summary>
        /// <param name="variable">功能码</param>
        /// <param name="isRead">true 读取 false写入</param>
        /// <param name="isBaseOne"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private (Functions, int) GetAddress(string variable, bool isRead, bool isBaseOne)
        {
            // 解析地址
            Functions func; // 功能码
            string area = variable[0].ToString();
            if (area == "0") // 线圈
                func = isRead ? Functions.RCoilStatus : Functions.WCoilStatus;
            else if (area == "1") // 写入线圈
                func = Functions.RInputCoils;
            else if (area == "3") // 读取寄存器
                func = Functions.RInputRegister;
            else if (area == "4") // 写入寄存器
                func = isRead ? Functions.RHoldingRegister : Functions.WHoldingRegister;
            else
                throw new Exception("地址格式不正确，无法识别");

            // 获取起始位置
            string s_addr = variable.Substring(1);
            int i_addr = int.Parse(s_addr);

            if (!isBaseOne) // 起始地址从0开始
                i_addr -= 1; 

            return (func, i_addr); // 返回功能码和起始地址
        }
    }
}
